#include "Headers.h"
#include "MarschnerM.h"
#include "MarschnerN.h"

GLFrame             viewFrame;
GLFrame             model_eye, model_light;
GLFrustum           viewFrustum;
GLMatrixStack		modelViewMatrix;
GLMatrixStack		projectionMatrix;
GLGeometryTransform	transformPipeline;
GLShaderManager		shaderManager;

GLFrame             lightFrame;
GLFrustum           lightFrustum;
GLMatrixStack		lightModelViewMatrix;
GLMatrixStack		lightProjectionMatrix;
GLGeometryTransform	lightTransformPipeline;

//GLTriangleBatch     lightBulb;

bool mouseLeftDown;
bool mouseRightDown;
float mouseX, mouseY;
float cameraAngleX = 0;
float cameraAngleY = 0;
float cameraDistance;
float lightAngle = 0;

GLfloat clipToTextureMatrix[] = {0.5, 0, 0, 0,
								0, 0.5, 0, 0,
								0, 0, 0.5, 0,
								0.5, 0.5, 0.5, 1};

GLuint fbo, DOMfbo;
GLuint depthTexture, DOM, mar[2];
GLuint DOMShader;
GLuint renderShader;
//GLuint flatShader;
GLint locMVP, locTexMatrix, locDepthTexture;
//GLint f_MVP, f_color;
GLint r_MVP, r_texMat, r_lightColor, r_lightPos, r_eyePos, r_DOM, r_depthMap, r_modelView, r_inverseModel, r_MarschnerM, 
r_MarschnerN;

GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 1.0f };
GLfloat vYellow[] = {1.0f, 1.0f, 0.0f, 1.0f};
GLfloat vWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};

const int MAR_HEIGHT = 256;
const int MAR_WIDTH = 256;

//double marM[MAR_HEIGHT][MAR_WIDTH][4];
float marM[MAR_HEIGHT * MAR_WIDTH * 4];
float marN[MAR_HEIGHT * MAR_WIDTH * 3];

void SetupRC()
{
	glClearColor(0.08f, 0.01f, 0.01f, 1.0f );

	glEnable(GL_DEPTH_TEST);

	shaderManager.InitializeStockShaders();

	//Initialize the camera and light frame;
	viewFrame.MoveForward(-150.0f);

	//lightFrame.TranslateLocal(0.0f, 0.0f, -150.0f);
	/*lightFrame.MoveForward(-150.0f);
	lightFrame.MoveRight(50.0f);*/

	lightFrame.MoveForward(-150.0f);
	//lightFrame.MoveRight(60.0f);
	//lightFrame.RotateWorld(m3dDegToRad(120.0), 0, 1, 0);
	lightFrame.MoveUp(30.0f);
	model_eye.RotateWorld(m3dDegToRad(90), 1, 0, 0);
	model_eye.RotateLocal(m3dDegToRad(180), 0, 0, 1);

	model_light.RotateWorld(m3dDegToRad(90), 1, 0, 0);
	model_light.RotateLocal(m3dDegToRad(180), 0, 0, 1);

	

	lightFrustum.SetPerspective(80.0f, 1.0f, 50.0f, 500.0f);
	lightProjectionMatrix.LoadMatrix(lightFrustum.GetProjectionMatrix());
	lightTransformPipeline.SetMatrixStacks(lightModelViewMatrix, lightProjectionMatrix);

	glGenFramebuffers(1, &fbo);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

	glActiveTexture(GL_TEXTURE0);
	glGenTextures(1, &depthTexture);
	glBindTexture(GL_TEXTURE_2D, depthTexture);
	//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); 
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, 512, 512, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

	//GLboolean checkTexture = glIsTexture(depthTexture);

	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
	//glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, depthTexture, 0);
	

	//fbo for the second pass
	glGenFramebuffers(1, &DOMfbo);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, DOMfbo);

	glActiveTexture(GL_TEXTURE1);
	glGenTextures(1, &DOM);
	glBindTexture(GL_TEXTURE_2D, DOM);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); 
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 512, 512, 0, GL_RGBA, GL_FLOAT, NULL);

	glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, DOM, 0);
	

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

	
	glGenTextures(2, mar);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, mar[0]);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );  

	MarM(MAR_HEIGHT, MAR_WIDTH, marM);
	//TestTexture(marM);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, MAR_WIDTH, MAR_HEIGHT, 0, GL_RGBA, GL_FLOAT, marM);


	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, mar[1]);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );  

	MarN(marN);
	//TestTexture(marM);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, MAR_WIDTH, MAR_HEIGHT, 0, GL_RGB, GL_FLOAT, marN);







	DOMShader = gltLoadShaderPairWithAttributes("DOM.vp", "DOM.fp", 1, GLT_ATTRIBUTE_VERTEX, 
		"position");

	locMVP = glGetUniformLocation(DOMShader, "modelViewProj");
	locTexMatrix = glGetUniformLocation(DOMShader, "texMatrix");
	locDepthTexture = glGetUniformLocation(DOMShader, "depthTexture");

	/*flatShader = gltLoadShaderPairWithAttributes("flatShader.vp", "flatShader.fp", 1, GLT_ATTRIBUTE_VERTEX,
		"position");

	f_MVP = glGetUniformLocation(flatShader, "modelViewProj");
	f_color = glGetUniformLocation(flatShader, "color");*/

	
	renderShader = gltLoadShaderPairWithAttributes("renderShader.vp", "renderShader.fp", 2,
		GLT_ATTRIBUTE_VERTEX, "pos", GLT_ATTRIBUTE_NORMAL, "tangent");

	r_MVP = glGetUniformLocation(renderShader, "modelViewProj");
	r_texMat = glGetUniformLocation(renderShader, "texMat");
	r_depthMap = glGetUniformLocation(renderShader, "depthMap");
	r_DOM = glGetUniformLocation(renderShader, "DOM");
	r_eyePos = glGetUniformLocation(renderShader, "eyePos");
	r_lightColor = glGetUniformLocation(renderShader, "lightColor");
	r_lightPos = glGetUniformLocation(renderShader, "lightPos");
	//r_modelView = glGetUniformLocation(renderShader, "modelView");
	r_inverseModel = glGetUniformLocation(renderShader, "inverseModelMat");
	r_MarschnerM = glGetUniformLocation(renderShader, "MarschnerM");
	r_MarschnerN = glGetUniformLocation(renderShader, "MarschnerN");
	

	gltCheckErrors();
	//gltMakeSphere(lightBulb, 10.0, 50, 50);

	LoadHairModel(HairFileName, myHairModel, dirs);
	
}

void ShutdownRC(void)
{}

void RenderScene(void)
{

	//The first pass. Render depth value to texture;
	model_eye.RotateWorld(cameraAngleX, 1, 0, 0);
	model_eye.RotateLocal(cameraAngleY, 0, 0, 1);
	cameraAngleX = 0;
	cameraAngleY = 0;

	


	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
	GLint viewPort[4];
	glGetIntegerv(GL_VIEWPORT, viewPort);
	glViewport(0, 0, 512, 512);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);


	//lightFrame.TranslateWorld(lightFrame.GetOriginX()*sin(cameraRotate)*cos((M3D_PI - cameraRotate)/2), 0, -lightFrame.GetOriginX()*sin(cameraRotate)*cos((M3D_PI - cameraRotate)/2));

	M3DMatrix44f lightCameraMatrix;
	lightFrame.GetCameraMatrix(lightCameraMatrix, false);
	lightModelViewMatrix.PushMatrix(lightCameraMatrix);

	lightModelViewMatrix.MultMatrix(model_light);


	shaderManager.UseStockShader(GLT_SHADER_FLAT, lightTransformPipeline.GetModelViewProjectionMatrix(), vBlue);
	DrawHairModel(myHairModel, dirs);
	
	/*glUseProgram(flatShader);
	glUniformMatrix4fv(f_MVP, 1, GL_FALSE, lightTransformPipeline.GetModelViewProjectionMatrix());
	glUniform4fv(f_color, 1,  vBlue);
	DrawHairModel(myHairModel, dirs);
	glUseProgram(0);*/
	
	lightModelViewMatrix.PopMatrix();// this line must be commented since the matrix stack should be applied 
	//to the opacity rendering pass.

	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	
	
	//The second pass. Render the deep opacity maps.
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, DOMfbo);
	glViewport(0, 0, 512, 512);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_DEPTH_TEST);
    glBlendFunc(GL_ONE, GL_ONE);
    glEnable(GL_BLEND);
	
	lightModelViewMatrix.PushMatrix(lightCameraMatrix);
	lightModelViewMatrix.MultMatrix(model_light);
	
	M3DMatrix44f textureMatrix;
	m3dMatrixMultiply44(textureMatrix, clipToTextureMatrix, lightTransformPipeline.GetModelViewProjectionMatrix());
	glUseProgram(DOMShader);
	glUniformMatrix4fv(locMVP, 1, GL_FALSE, lightTransformPipeline.GetModelViewProjectionMatrix());
	glUniformMatrix4fv(locTexMatrix, 1, GL_FALSE, textureMatrix);
	glUniform1i(locDepthTexture, 0);
	DrawHairModel(myHairModel, dirs);
	glUseProgram(0);

	lightModelViewMatrix.PopMatrix();
	glDisable(GL_BLEND);
	
	
	//The last rendering pass.
	
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	viewFrame.MoveForward(cameraDistance);
	cameraDistance = 0;

	M3DMatrix44f cameraMatrix;
	viewFrame.GetCameraMatrix(cameraMatrix, false);
	modelViewMatrix.PushMatrix(cameraMatrix);

	modelViewMatrix.MultMatrix(model_eye);

	M3DMatrix44f modelMatrix, inverseModelMatrix;
	model_eye.GetMatrix(modelMatrix, false);
	m3dInvertMatrix44(inverseModelMatrix, modelMatrix);

	glUseProgram(renderShader);
	glUniformMatrix4fv(r_MVP, 1, GL_FALSE, transformPipeline.GetModelViewProjectionMatrix());
	glUniformMatrix4fv(r_texMat, 1, GL_FALSE, textureMatrix);
	//glUniformMatrix4fv(r_modelView, 1, GL_FALSE, transformPipeline.GetModelViewMatrix());
	glUniformMatrix4fv(r_inverseModel, 1, GL_FALSE, inverseModelMatrix);
	glUniform3fv(r_lightColor, 1, vYellow);

	M3DVector3f lightPos, modelLightPos;
	lightFrame.GetOrigin(lightPos);// here there is a mistake, the lightPos should be translated under the effect of model_light matrix.
	
	/*M3DMatrix44f modelLightMat, inverseModelLightMat;
	model_light.GetMatrix(modelLightMat, false);
	m3dInvertMatrix44(inverseModelLightMat, modelLightMat);*/
	//model_light.WorldToLocal(lightPos, modelLightPos);
	glUniform3fv(r_lightPos, 1, lightPos);
	
	M3DVector3f eyePos;
	viewFrame.GetOrigin(eyePos);
	glUniform3fv(r_eyePos, 1, eyePos);

	glUniform1i(r_depthMap, 0);
	glUniform1i(r_DOM, 1);
	glUniform1i(r_MarschnerM, 2);
	glUniform1i(r_MarschnerN, 3);

	DrawHairModel(myHairModel, dirs);
	glUseProgram(0);

	//shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlue);
	//lightBulb.Draw();

	modelViewMatrix.PopMatrix();
	

	/*
	//The test pass. Render to screen.
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
	glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	viewFrame.MoveForward(cameraDistance);

	M3DMatrix44f cameraMatrix;
	viewFrame.GetCameraMatrix(cameraMatrix, false);
	modelViewMatrix.PushMatrix(cameraMatrix);
	
	modelViewMatrix.MultMatrix(model_eye);

    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
	shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed);

	DrawHairModel(myHairModel, dirs);
	modelViewMatrix.PopMatrix();
	*/
	
	
	glutSwapBuffers();

}

void ChangeSize(int w, int h)
{
	glViewport(0, 0, w, h);

	viewFrustum.SetPerspective(80.0f, float(w)/float(h), 50.0f, 300.0f);

	projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
	transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

void Mouse(int button, int state, int x, int y)
{
	mouseX = x;
    mouseY = y;

    if(button == GLUT_LEFT_BUTTON)
    {
        if(state == GLUT_DOWN)
        {
            mouseLeftDown = true;
        }
        else if(state == GLUT_UP)
            mouseLeftDown = false;
    }

    else if(button == GLUT_RIGHT_BUTTON)
    {
        if(state == GLUT_DOWN)
        {
            mouseRightDown = true;
        }
        else if(state == GLUT_UP)
            mouseRightDown = false;
    }
}

void MouseMotion(int x, int y)
{
	cameraAngleX = 0;
	cameraAngleY = 0;
	cameraDistance = 0;

	if(mouseLeftDown)
    {
        cameraAngleY += (x - mouseX) * 0.1f;
        cameraAngleX += (y - mouseY) * 0.1f;
        mouseX = x;
        mouseY = y;
    }
    if(mouseRightDown)
    {
        cameraDistance += (y - mouseY) * 0.2f;
        mouseY = y;
    }


    glutPostRedisplay();
}

void SpecialKeys(int key, int x, int y)
{
	float linear = 10.0f;
	float angular = float(m3dDegToRad(5.0f));
	
	if(key == GLUT_KEY_UP)
		viewFrame.MoveForward(linear);
	
	if(key == GLUT_KEY_DOWN)
		viewFrame.MoveForward(-linear);
	
	if(key == GLUT_KEY_LEFT)
		//viewFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
		viewFrame.TranslateLocal(-linear, 0, 0);
	
	if(key == GLUT_KEY_RIGHT)
		//viewFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);	
		viewFrame.TranslateLocal(linear, 0, 0);

	if(key == GLUT_KEY_HOME)
		viewFrame.RotateWorld(angular, 1.0f, 0.0f, 0.0f);

	if(key == GLUT_KEY_END)
		viewFrame.RotateWorld(-angular, 1.0f, 0.0f, 0.0f);

	glutPostRedisplay();
}


static void Idle(void)
{
  //model_light.RotateLocal(0.08f, 0, 0, 1);
  //lightFrame.RotateWorld(0.08f, 1, 0, 0);
  glutPostRedisplay();
}


static void Keyboard(unsigned char c, int x, int y)
{
  static int animating = 0,
             repeat = 0;

  switch (c) {
  case ' ':
    animating = !animating; /* Toggle */
    if (animating) {
      glutIdleFunc(Idle);
    } else {
      glutIdleFunc(NULL);
    }    
    break;
  
  case 27:  /* Esc key */
    exit(0);
    break;
  }
}



int main(int argc, char* argv[])
{
	gltSetWorkingDirectory(argv[0]);
		
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(512,512);
  
    glutCreateWindow("Deep Opacity Maps demo");
 
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
	glutKeyboardFunc(Keyboard);
    glutSpecialFunc(SpecialKeys);
	glutMouseFunc(Mouse);
    glutMotionFunc(MouseMotion);

	GLenum err = glewInit();
	if (GLEW_OK != err) {
		fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
		return 1;
		}

    SetupRC();
    glutMainLoop();    
    ShutdownRC();
    return 0;
}